package com.atguigu.tingshu.album.service.impl;

import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.album.service.TestService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-10-07 08:54
 */
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


   /* @Override
    public synchronized void testLock() {
        try {
            //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
            String value = stringRedisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(value)) {
                return;
            }
            //2.对获取到值进行+1操作
            int num = Integer.parseInt(value);
            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));

        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }*/


    private static final String lockKey = "lock";
    //private static final String lockVal = "lock";

    /**
     * 自定义分布式锁
     */
   /* @Override
    public void testLock() {
        //一、尝试获取分布式锁
        //版本3:分布式锁实现 set k v EX  NX 实现 一个命令完成加锁跟设置锁过期时间
        //问题：获取锁线程执行业务超时，锁自动释放，其他进程获取锁成功，阻塞线程恢复后将其他线程锁释放导致分布式锁失效（违背排他性）
        //解决：在释放锁前判断锁是否为当前线程，在锁值上做变动（加入线程信息）
        //Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);

        //版本4：在3基础上，锁的值加入线程信息，在删除锁前判断当前线程是否为持有锁线程、
        String lockVal = IdUtil.fastSimpleUUID();
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);

        //二、获取锁成功后执行业务逻辑
        if (flag) {
            try {
                //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
                String value = stringRedisTemplate.opsForValue().get("num");
                if (StringUtils.isBlank(value)) {
                    return;
                }
                //2.对获取到值进行+1操作
                int num = Integer.parseInt(value);
                stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (NumberFormatException e) {
                e.printStackTrace();
            } finally {
                //三、将锁释放
                //stringRedisTemplate.delete(lockKey);
                //版本4释放锁前加一层判断
                //if (lockVal.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                //    //说明锁是当前线程
                //    stringRedisTemplate.delete(lockKey);
                //}
                //版本5：采用lua脚本避免锁互相释放 确保判断跟删除原子性
                String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(scriptText);
                redisScript.setResultType(Long.class);
                stringRedisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
            }
        } else {
            try {
                //获取锁失败-业务需求是否需要重试
                Thread.sleep(200);
                this.testLock();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }*/


    @Autowired
    private RedissonClient redissonClient;

    /**
     * 采用Redisson框架实现分布式锁
     */
    @Override
    public void testLock() {
        //一、获取分布式锁
        //1.创建锁对象
        RLock lock = redissonClient.getLock("myLock");
        //2.获取分布式锁
        //2.1 线程一致阻塞到加锁成功为止
        lock.lock();
        //2.2 tryLock（）如果锁可用，则获取锁，并立即返回值true。如果锁不可用，则此方法将立即返回值false。
        //boolean flag = lock.tryLock();
        //2.3 如果锁在给定的等待时间内是空闲的，并且当前线程没有被中断，则获取锁
        //boolean flag = lock.tryLock(3, TimeUnit.SECONDS);
        //2.4 如果锁在给定的等待时间内是空闲的，并且当前线程没有被中断，则获取锁,获取成功后锁过期时间使用p2参数(锁过期时间)
        //boolean flag = lock.tryLock(3, 5, TimeUnit.SECONDS);
        //二、获取锁成功执行业务逻辑
        try {
            //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
            String value = stringRedisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(value)) {
                return;
            }
            //2.对获取到值进行+1操作
            int num = Integer.parseInt(value);
            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));

        } catch (NumberFormatException e) {
            e.printStackTrace();
        } finally {
            //三、释放锁
            lock.unlock();
        }
    }

    /**
     * 读数据方法：
     */
    @Override
    public void read() {
        try {
            //1.获取分布式读写锁（获取读锁）
            RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock:1");
            RLock rLock = rwlock.readLock();
            //rLock.lock();
            boolean b = rLock.tryLock(6, 5, TimeUnit.SECONDS);
            //2.查询缓存 命中缓存直接返回
            //3.未命中查询数据库，将查询结果放入缓存返回结果
            if (b) {
                System.out.println("获取到读锁，执行查询业务");
            }
            //4.将读锁释放 TODO 5s会自动释放
            //rLock.unlock();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 写数据方法
     */
    @Override
    public void write() {
        try {
            //1.获取分布式读写锁（获取写锁）
            RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock:1");
            RLock rLock = rwlock.writeLock();
            boolean b = rLock.tryLock(6, 5, TimeUnit.SECONDS);
            if (b) {
                //2.修改数据库
                System.out.println("获取到写锁，执行写数据库操作");
            }
            //3.将写锁释放 5s后写锁自动释放
            //rLock.unlock();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
